package algo;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import model.DebateFormat;
import model.Speaker;
import model.SpeakerRole;
import tools.Translator;
import algo.DistributionResult.SingleDebateDistribution;

import com.google.inject.Inject;

public class SimpleDistribution implements DistributionAlgorithm {

	static boolean ROLE_GROUP = true;
	static boolean SPEAKER_GROUP = !ROLE_GROUP;

	private final Set<DebateFormat> formats;
	private final Set<SpeakerRole> roles;
	private Translator tr;
	private final BipartiteMatcher matcher;

	@Inject
	public SimpleDistribution(Set<DebateFormat> formats, Set<SpeakerRole> roles, Translator tr,
			BipartiteMatcher matcher) {
		this.formats = formats;
		this.roles = roles;
		this.matcher = matcher;
		this.tr = tr;
	}

	@Override
	public DistributionResult getResult(List<Speaker> speakers,
			Map<DebateFormat, Integer> debateCounts) throws MatchingException {

		// Determine min-max Count
		int minCount = 0;
		int maxCount = 0;
		for (DebateFormat format: formats) {
			minCount += debateCounts.get(format) * format.getMinSpeakerCount();
			maxCount += debateCounts.get(format) * format.getMaxSpeakerCount();
		}

		int speakerCount = speakers.size();
		if (speakerCount < minCount) {
			throw new MatchingException(tr.tr("TOO_FEW_SPEAKERS") + " "
					+ tr.tr("AT_LEAST") + minCount + tr.tr("SPEAKERS_REQUIRED"));
		}

		if (speakerCount > maxCount) {
			throw new MatchingException(tr.tr("TOO_MANY_SPEAKERS") + " "
					+ tr.tr("MAXIMAL") + maxCount + tr.tr("SPEAKERS_SUPPORTED"));
		}

		// calculate places still to be filled
		int freePlaces = speakerCount - minCount;
		
		Map<DebateFormat, Integer> freeSlots = new HashMap<>();
		for (DebateFormat format: formats) {
			freeSlots.put(format, 0);
		}
		
		while (freePlaces > 0) {
			for (DebateFormat format: formats) {
				int current = freeSlots.get(format);
				if (current < debateCounts.get(format) * (format.getMaxSpeakerCount() - format.getMinSpeakerCount())) {
					freeSlots.put(format, current + 1);
					freePlaces--;
				}
			}
		}
		
		// shuffle all speakers to get guaranteed random results
		// Collections.shuffle(speakers);

		Set<DebateVertex> verts = new HashSet<>();

		Map<SpeakerRole, Set<DebateVertex>> roleMapping = new HashMap<>();

		for (SpeakerRole role: roles) {
			roleMapping.put(role, new HashSet<DebateVertex>());
		}
		// add all roles
		for (DebateFormat format: formats) {
			int[] freePlacesPerDebate = new int[debateCounts.get(format)];
			for (int i = 0; i < freeSlots.get(format); i++) {
				freePlacesPerDebate[i % debateCounts.get(format)]++;
			}
			for (int i = 0; i < debateCounts.get(format); i++) {
				List<SpeakerRole> roles = format.getRolesForSpeakerCount(format.getMinSpeakerCount() + freePlacesPerDebate[i]);
				for (SpeakerRole role: roles) {
					DebateVertex newVert = new DebateVertex(role, i, format);
					roleMapping.get(role).add(newVert);
					verts.add(newVert);
				}
			}
		}

		for (Speaker s: speakers) {
			DebateVertex speakerVertex = new DebateVertex(s);
			// add speaker vertices and add their roles
			verts.add(speakerVertex);
			for (SpeakerRole r : s.getIncludedRoles()) {
				for (DebateVertex neigh : roleMapping.get(r)) {
					neigh.neighbours.add(speakerVertex);
					speakerVertex.neighbours.add(neigh);
				}
			}
		}

		// calculate matching
		DebateVertex unmatched = matcher.getResult(verts);
		if (unmatched != null) {
			// matching was not successful
			if (unmatched.speaker != null) {
				throw new MatchingException(unmatched.speaker);
			}
			else {
				// a role must be unmatched
				throw new MatchingException(unmatched.role);
			}
		}

		int debateCount = 0;
		for (DebateFormat format: formats) {
			debateCount += debateCounts.get(format);
		}
		
		// Calculate result object
		SingleDebateDistribution[] distributions = new SingleDebateDistribution[debateCount];
		int index = 0;
		for (DebateFormat format: formats) {
			for (int i = 0; i < debateCounts.get(format); i++) {
				List<Entry<SpeakerRole, Speaker>> curMapping = new ArrayList<>();
				for (DebateVertex vert: verts) {
					if (vert.debateNumber == i && vert.format == format && vert.group == ROLE_GROUP) {
						curMapping.add(new AbstractMap.SimpleEntry<SpeakerRole, Speaker>(vert.role, vert.match.speaker));
					}
				}
				distributions[index++] = new SingleDebateDistribution(format, curMapping);
			}
		}
		
		return new DistributionResult(distributions);
	}

}